from typing import Optional
from asyncio import Lock
from contextlib import asynccontextmanager
from fastapi import UploadFile, BackgroundTasks
from app.core.storage.factory import StorageFactory
from app.models.storage_provider_model import FileModel, StorageProviderModel
from app.schemas.common import PaginationResult
from app.schemas.file_schemas import FileResponse, FileListRequest
from app.schemas.response import Response
from app.core.exceptions import APIException
from app.core.constants import FileStatusEnum
from logging import getLogger

logger = getLogger(__name__)


class FileService:
    """文件服务"""

    def __init__(self):
        self._upload_locks = {}  # MD5 -> Lock 的映射

    @asynccontextmanager
    async def _get_upload_lock(self, md5: str):
        """获取上传锁，防止重复上传"""
        if md5 not in self._upload_locks:
            self._upload_locks[md5] = Lock()

        try:
            await self._upload_locks[md5].acquire()
            yield
        finally:
            self._upload_locks[md5].release()
            if not self._upload_locks[md5].locked():
                del self._upload_locks[md5]

    async def upload_file(
        self, file: UploadFile, user_id: int, background_tasks: BackgroundTasks, provider_id: Optional[int] = None
    ) -> Response[FileResponse]:
        """上传文件"""
        try:
            # 获取存储策略
            storage = await StorageFactory.get_storage(provider_id)

            # 计算MD5
            md5 = await storage.calculate_md5(file)

            # 使用锁确保同一文件不会并发上传
            async with self._get_upload_lock(md5):
                # 检查文件是否已存在
                existing_file = await FileModel.filter(md5=md5).first()
                if existing_file:
                    return Response.success(data=await self._to_response(existing_file))

                # 获取供应商
                provider = await StorageProviderModel.get_or_none(
                    id=provider_id if provider_id else storage.provider_id
                )
                if not provider:
                    raise APIException("存储供应商不存在")

                # 对于大文件（>5MB），使用分片上传
                if file.size > 5 * 1024 * 1024:
                    # 先创建数据库记录
                    file_model = await FileModel.create(
                        filename=file.filename,
                        provider=provider,
                        size=file.size,
                        content_type=file.content_type,
                        md5=md5,
                        uploader_id=user_id,
                        status=FileStatusEnum.UPLOADING,
                    )

                    # 将实际的文件上传放入后台任务
                    background_tasks.add_task(self._upload_large_file, file, file_model.id, storage)

                    return Response.success(data=await self._to_response(file_model))

                # 小文件直接上传
                storage_path = await storage.save(file, file.filename)

                # 保存文件信息
                file_model = await FileModel.create(
                    filename=file.filename,
                    storage_path=storage_path,
                    provider=provider,
                    size=file.size,
                    content_type=file.content_type,
                    md5=md5,
                    uploader_id=user_id,
                    status=FileStatusEnum.COMPLETED,
                )

                return Response.success(data=await self._to_response(file_model))

        except Exception as e:
            logger.error(f"文件上传失败: {str(e)}")
            raise APIException("文件上传失败")

    async def _upload_large_file(self, file: UploadFile, file_id: int, storage: "BaseStorage"):
        """分片上传大文件"""
        file_model = await FileModel.get(id=file_id)

        try:
            # 初始化分片上传
            upload_id = await storage.init_multipart_upload(file.filename, file.content_type)

            parts = []
            part_number = 1
            chunk_size = storage.chunk_size

            # 分片上传
            while chunk := await file.read(chunk_size):
                etag = await storage.upload_part(upload_id, part_number, chunk)
                parts.append({"PartNumber": part_number, "ETag": etag})
                part_number += 1

            # 完成分片上传
            storage_path = await storage.complete_multipart_upload(upload_id, parts)

            # 更新文件记录
            await file_model.update(storage_path=storage_path, status=FileStatusEnum.COMPLETED)

        except Exception as e:
            logger.error(f"大文件上传失败: {str(e)}")
            await file_model.update(status=FileStatusEnum.FAILED)
            # 清理分片
            if upload_id:
                await storage.abort_multipart_upload(upload_id)

    async def get_file_list(self, request: FileListRequest) -> Response[PaginationResult[FileResponse]]:
        """获取文件列表"""
        try:
            # 构建查询
            query = FileModel.all()
            if request.filename:
                query = query.filter(filename__icontains=request.filename)

            # 只查询需要的字段
            query = query.only("id", "filename", "size", "content_type", "created_at", "uploader__username")

            # 获取总数
            total = await query.count()

            # 分页查询
            files = await query.offset((request.page - 1) * request.page_size).limit(request.page_size)

            # 转换响应
            items = [await self._to_response(file) for file in files]

            return Response.success(
                data=PaginationResult(items=items, total=total, page=request.page, page_size=request.page_size)
            )

        except Exception as e:
            logger.error(f"获取文件列表失败: {str(e)}")
            raise APIException("获取文件列表失败")

    async def delete_file(self, file_id: int, user_id: int) -> Response:
        """删除文件"""
        try:
            # 获取文件信息
            file = await FileModel.get_or_none(id=file_id)
            if not file:
                raise APIException("文件不存在")

            # 获取存储策略
            storage = await StorageFactory.get_storage(file.provider_id)

            # 删除文件
            if await storage.delete(file.storage_path):
                await file.delete()
                return Response.success()
            else:
                raise APIException("文件删除失败")

        except Exception as e:
            logger.error(f"文件删除失败: {str(e)}")
            raise APIException("文件删除失败")

    async def get_file_url(self, file_id: int) -> Response[str]:
        """获取文件访问URL"""
        try:
            # 获取文件信息
            file = await FileModel.get_or_none(id=file_id)
            if not file:
                raise APIException("文件不存在")

            # 获取存储策略
            storage = await StorageFactory.get_storage(file.provider_id)

            # 获取URL
            url = await storage.get_url(file.storage_path)
            return Response.success(data=url)

        except Exception as e:
            logger.error(f"获取文件URL失败: {str(e)}")
            raise APIException("获取文件URL失败")

    async def _to_response(self, file: FileModel) -> FileResponse:
        """转换为响应模型"""
        storage = await StorageFactory.get_storage(file.provider_id)
        url = await storage.get_url(file.storage_path) if file.storage_path else None

        return FileResponse(
            id=file.id,
            filename=file.filename,
            size=file.size,
            content_type=file.content_type,
            url=url,
            created_at=file.created_at,
            uploader_name=file.uploader.username if file.uploader else None,
        )

    async def get_file_for_download(self, file_id: int) -> FileModel:
        """获取文件用于下载"""
        file = await FileModel.get_or_none(id=file_id)
        if not file:
            raise APIException("文件不存在")

        if file.status != FileStatusEnum.COMPLETED:
            raise APIException("文件未准备就绪")

        storage = await StorageFactory.get_storage(file.provider_id)
        file_obj = await storage.get(file.storage_path)
        if not file_obj:
            raise APIException("文件获取失败")

        return file
